Skip to content

test: verify Telegram successor against current dev - #11

Draft
twoimo wants to merge 17 commits into
mainfrom
successor/telegram-tool-activity-clean
Draft

test: verify Telegram successor against current dev#11
twoimo wants to merge 17 commits into
mainfrom
successor/telegram-tool-activity-clean

Conversation

@twoimo

@twoimo twoimo commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Internal verification PR. Base mirrors upstream dev at 10a32cf; head contains only the Telegram successor diff and final rejected-terminal repair.

Yeachan-Heo and others added 2 commits July 25, 2026 04:27
…ispose flake (Yeachan-Heo#3144)

* test(sdk): make teardown ordering witness production-observable

The Phase 2 rewrite of "session teardown drains admitted direct gate
resolution" was accepted with a narrowed claim because a mutation probe
survived: replacing `await rt.waitForGateResolutionQuiescence()` with
`void ...` still passed. The test fully mocked `resolveGate`, so the
delayed operation never touched the real terminal controller, and it
released the resolver after a single setImmediate while `stopSession()`
was independently awaiting the native `pushFrameAndWait(session_closed)`
barrier — resolution therefore completed before teardown reached
detachment even without the quiescence await.

Now the test calls through the original `registerGateTerminalController`
and the original `resolveGate` (wrapping the latter only with a deferred
pre-terminalization gate), makes `pushFrameAndWait(session_closed)` an
explicit test-controlled pre-drain barrier, asserts the controller is
still attached at the quiescence point, then observes real accepted
terminalization and gate continuation before detachment.

Mutation-proved: the void-await mutation now FAILS at the pre-detachment
assertion (controllerAttached false). Passes 10/10 unmutated.

* test(runtime): poll for the TERM marker in the concurrent-dispose redteam

Caught live by the stabilization soak: main-nontag rehearsal run
30149261910 failed on shard 11 with

  (fail) process-lifecycle adversarial owned-process invariants >
         double and concurrent dispose share one settled result and
         issue one terminating signal
  expect(received).toHaveLength(expected)  Expected: 1  Received: 0

The child's TERM trap appends its marker asynchronously
(`trap 'echo term >> $tmp; exit 0' TERM`), so under shard load
`awaitExit` can return before that write lands and the single-sample
read observes an empty file. The file already has a `waitForAsync`
helper for exactly this shape; the marker assertion just wasn't using
it.

Polls for the single terminating signal before asserting, preserving
the original invariant (exactly one `term` line — not "at least one").

Verified: 15/15 reruns, 4x parallel contention clean, whole file 9/9,
typecheck and biome clean.

This test was NOT in the 62-suspect audit shortlist: it did not fail or
retry during the mined two-week window, so it is a genuinely new
observation the soak surfaced.

---------

Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
@twoimo
twoimo changed the base branch from tmp/upstream-dev-10a32cfd to main July 25, 2026 08:14
@twoimo twoimo closed this Jul 25, 2026
@twoimo twoimo reopened this Jul 25, 2026
`sh` runs a TERM trap only after the current foreground command returns.
With the child looping on `sleep 1` and `gracefulMs: 500`, dispose could
escalate to SIGKILL before the handler wrote its `term` marker, so the
one-terminating-signal assertion saw an empty file (observed in CI run
30149261910, coding-agent shard 11).

Shorten the loop interval to 0.05s and use the module's own
`DEFAULT_GRACEFUL_MS` (2000ms) so the trap has a deterministic window.
Under 18-worker CPU contention the old shape wins the race 8/20; the new
shape wins 20/20. All assertions are unchanged.

Also record the missing `## [Unreleased]` changelog entries for Yeachan-Heo#3109,
Yeachan-Heo#3127, and Yeachan-Heo#3131, found while auditing release scope after v0.11.9.
@twoimo twoimo closed this Jul 25, 2026
@twoimo twoimo reopened this Jul 25, 2026
@twoimo twoimo closed this Jul 25, 2026
@twoimo twoimo reopened this Jul 25, 2026
@twoimo twoimo closed this Jul 25, 2026
@twoimo twoimo reopened this Jul 25, 2026
@twoimo twoimo closed this Jul 25, 2026
@twoimo twoimo reopened this Jul 25, 2026
@twoimo twoimo closed this Jul 25, 2026
@twoimo twoimo reopened this Jul 25, 2026
@twoimo twoimo closed this Jul 25, 2026
@twoimo twoimo reopened this Jul 25, 2026
Yeachan-Heo and others added 2 commits July 25, 2026 18:17
…turns (Yeachan-Heo#3147)

Lifecycle `session_shutdown` started `controller.stopCurrentSession(ctx)`
but discarded the promise, awaiting only `stopSession(id)`. Once startup
has settled the host is broker-visible and can accept `session.close`
while the startup handler's post-start `reconcileCurrentSession` is still
running. That reconciliation can mint a replacement notification-root
token; `ensureTelegramDaemon` then unregisters it asynchronously. Shutdown
could therefore return — and disposal exit — before that unregister's file
lock and atomic registry write settled, leaving a stale `sessions[id]` row
that the retained older token is correctly fenced from removing
(`unregisterNotificationRoot` rejects token mismatches by design).

Now shutdown snapshots `sessionStartPromises.has(id)` first and awaits the
settled controller stop after `stopSession` whenever startup was NOT
pending, so completed-start reconciliation and its replacement-token
cleanup are joined. The intentional nonblocking path is preserved exactly
where it matters: a genuinely pending startup entry (the `/notify on`
case) still leaves the controller stop fire-and-forget.

Surfaced by the flaky-CI stabilization soak. The regression test
"Telegram root release failure is retained and retried through lifecycle
shutdown" failed deterministically on darwin-arm64 at dev head while
Linux CI stayed green (run 30147146988, 34/34 shards) — a completion-
ordering divergence, not a `/var` canonicalization or native-addon issue:
both registry and notification-root paths are lexical `path.join` with no
realpath or case folding.

Verified on darwin-arm64: the previously-failing test now passes 10/10,
the whole sdk-host-wiring file is 72/72 (first fully green run of this
file on Darwin), and telegram daemon + btw-e2e are 463/463. The assertion
was kept intact rather than replaced with polling, which would have hidden
the lifecycle-return bug.

Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
@twoimo twoimo closed this Jul 25, 2026
@twoimo twoimo reopened this Jul 25, 2026
…eachan-Heo#3149)

Caught by the stabilization soak: main-nontag rehearsal run 30151115867
failed on shard 8 with

  (fail) session_start swallows startup plus owner-release failure
         without surfacing an extension error
  expect(surfaced).toEqual([])   received 1 surfaced error

The test used `mockRejectedValueOnce` on the SHARED prototypes
`NotificationServer.prototype.start` and `SessionSdkHost.prototype.stop`.
That is a one-shot global: a peer test scheduled concurrently in the same
shard can consume the single rejection first. When that happens this
test's own `start()` resolves, so `startSession` never reaches the
`stopSession` catch that sets `suppressExtensionError`
(src/sdk/bus/index.ts:4407-4420), and the startup error surfaces through
`session_start` instead of being swallowed.

Three separate tests in this file arm one-shot rejections on the same two
prototypes, so the interference is cross-test within a shard rather than
a missing restore — every one of them restores correctly.

Replaces both one-shots with per-test guarded implementations that reject
only on this test's first call and then delegate to the real method,
preserving the existing assertions (including
`expect(hostStop).toHaveBeenCalledTimes(2)`, which is what makes the
"first call rejects" semantics load-bearing).

Verified on darwin-arm64: 10/10 targeted reruns, 6x concurrent whole-file
runs with zero failures, whole file 72/72, typecheck and biome clean.

Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
@twoimo twoimo closed this Jul 25, 2026
@twoimo twoimo reopened this Jul 25, 2026
Yeachan-Heo and others added 3 commits July 25, 2026 20:58
…#3139) (Yeachan-Heo#3146)

The release CI default was narrowed to `contents: read` in Yeachan-Heo#3136 with
`publish` retaining the sole job-level `contents: write` override, but no
automated policy test protected that invariant. `dev-ci.yml` also had no
explicit permission block and inherited the repository default.

Add `scripts/check-workflow-permissions.ts`: a deterministic offline
default-deny evaluator plus CLI that parses committed workflow YAML and
requires every workflow to declare a least-privilege `permissions` block.
Write scopes are denied everywhere except one explicit allowlist triple
(ci.yml / publish / contents), and the three known workflows must declare
exactly `contents: read`. Violations name workflow, job, permission path,
actual, and expected.

Codify `contents: read` on `dev-ci.yml` after auditing its jobs: they only
check out, install, test, and exchange artifacts through the Actions
artifact API, with no `git push`, `gh` CLI, `secrets.*`, or release/PR
mutation, so no write-scoped token is required.

Back it with real-YAML mutation proofs that read committed workflows from
disk, mutate the parsed document, and re-evaluate -- covering workflow and
job `write`, `write-all`, extra scopes, deleted permission blocks, and
malformed job shapes. Route the regression through affected-path CI in both
the broad and targeted planners so workflow and CI-harness changes select it
alongside the existing release-policy suite, and pin the invariant in
`release-policy.test.ts` as well.

Refs Yeachan-Heo#3139

Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…/ resolver (Yeachan-Heo#3145)

Closes non-blocking follow-up from the Yeachan-Heo#3080 post-merge review: cleanup_pending marker asymmetry between the async gate and the sync resolver.

Reviewed exact head 90aa337 with durable MERGE_READY and green exact-head CI. Merged after post-merge Dev CI 30157160426 on 352a2c1 turned terminal green.
…o what shipped (Yeachan-Heo#3140)

Documentation-only correction of readiness claims from Yeachan-Heo#3080.

Exact-head MERGE_READY at 6cf2764 with green CI. Held during red/nonterminal post-merge gates; merged after Dev CI 30158413804 on 2317939 terminalized success.
jaeyunha and others added 2 commits July 25, 2026 13:14
…3070)

Exact-head MERGE_READY at 332942d with green CI. Merged after post-merge Dev CI 30158899514 on e108816 terminalized success.
…eachan-Heo#3132)

`retryDelayMs()` honoured the server's `Retry-After` verbatim:

    if (Number.isFinite(seconds)) return Math.max(baseline, Math.max(0, seconds * 1000));

There was no upper bound, so a hostile or misconfigured endpoint could stall
the usage fetch for as long as it liked. Verified: `Retry-After: 86400` made
the provider sleep 86_400_000ms (24 hours), and the HTTP-date form did the
same (86_399_386ms). The sleep is abort-aware, but with no signal — or a
long-lived one — the usage refresh simply hangs, and auth-storage ranks
credentials off this call.

Every sibling path in the repo already bounds this:
  - utils/fetch-retry.ts  DEFAULT_MAX_DELAY_MS = 60_000 (+ fail-fast above cap)
  - providers/openai-bounded-rate-limits.ts  OPENAI_RETRY_DELAY_CAP_MS = 60_000
  - session/agent-session.ts  maxAcceptableDelayMs = 30_000
Only the Claude usage path was missing a ceiling.

Fix: add MAX_RETRY_DELAY_MS = 60_000 and clamp through clampRetryDelay(),
which also drops non-finite/negative hints. 60s matches the existing
convention and preserves both pre-existing assertions exactly
(`Retry-After: 1` → 1000, `Retry-After: 60` → 60_000).

Regression coverage (test/claude-usage-retry.test.ts): absurd numeric
Retry-After, absurd HTTP-date Retry-After, and a negative Retry-After. The
first two fail against the unfixed code (received 86_400_000 / 86_399_386,
expected 60_000).

Gates: claude-usage-retry (11 pass), usage suite (28 pass across 5 files),
tsc --noEmit clean.

Co-authored-by: dmae97 <dmae97@users.noreply.github.com>
@twoimo twoimo closed this Jul 25, 2026
@twoimo twoimo reopened this Jul 25, 2026
@github-actions

Copy link
Copy Markdown

TELEGRAM_SUCCESSOR_CONFLICTS

packages/coding-agent/src/sdk/bus/index.ts

diff --cc packages/coding-agent/src/sdk/bus/index.ts
index b7d9432b,aa9aaea5..00000000
--- a/packages/coding-agent/src/sdk/bus/index.ts
+++ b/packages/coding-agent/src/sdk/bus/index.ts
@@@ -5172,22 -5235,10 +5235,26 @@@ export function createNotificationsExte
  		await Promise.allSettled([...branchStartupTasks]);
  		const id = sessionId(ctx);
  		const rt = runtimes.get(id);
++<<<<<<< ours
 +		if (rt) terminalizeInFlightTools(rt, id, "unknown");
 +		// Startup is only genuinely in flight when a `sessionStartPromises` entry
 +		// exists. Once startup has settled, the host is broker-visible and its
 +		// post-start `reconcileCurrentSession` may already have minted a
 +		// replacement notification-root token whose unregister is still awaiting
 +		// its file lock and atomic registry write. Returning before that settles
 +		// leaves a stale `sessions[id]` row that the retained older token is
 +		// correctly fenced from removing, so shutdown must join it.
 +		const startupWasPending = sessionStartPromises.has(id);
++=======
+ 		if (rt) terminalizeInFlightTools(rt, id, "cancelled");
++>>>>>>> theirs
  		const controllerStop =
  			typeof ctx.sessionManager.getCwd === "function" ? controller.stopCurrentSession(ctx) : Promise.resolve(false);
 -		void controllerStop.catch(error => logger.warn(`notifications: controller shutdown failed: ${String(error)}`));
 +		const settledControllerStop = controllerStop.catch(error => {
 +			logger.warn(`notifications: controller shutdown failed: ${String(error)}`);
 +			return false;
 +		});
 +		if (startupWasPending) void settledControllerStop;
  		try {
  			await stopSession(id);
  		} catch (error) {

@twoimo twoimo closed this Jul 25, 2026
Owner-approved MERGE_READY at exact head 4caf43c with green CI (31 checks). Merged after post-Yeachan-Heo#3132 Dev CI 30159910672 on 77c9a60 terminalized success. Yeachan-Heo#3134 became dirty and was skipped under one-at-a-time sequencing.
@twoimo twoimo reopened this Jul 25, 2026
Yeachan-Heo and others added 2 commits July 25, 2026 23:08
…tones (rebase of Yeachan-Heo#3069) (Yeachan-Heo#3150)

* perf(coding-agent): skip lock acquisition for already-completed tombstones

Rebase of Yeachan-Heo#3069 (exact head 2b58273) onto
current dev.

`reconcileManagedTombstones` acquired a durable fsync-backed operation lock for
every tombstone in the scope, including tombstones whose targets had already
published a `cleanup_completed` receipt and therefore had nothing left to
reconcile. Scopes that accumulate many completed tombstones paid that cost on
every `gjc resume` and delete.

Skip the lock only when every target of the tombstone still satisfies the
identity-bound `cleanupCompleted` predicate, which verifies schema version,
state, scope digest, tombstone path, attempt, target path/sessionId/cwd, and the
full identity tuple (dev, ino, size, mtimeNs, sha256). Any target that fails the
predicate falls through to the existing locked reconciliation path unchanged, so
this is never a blanket skip.

Tests:
- untouched completed receipts -> zero lock acquisitions
- a completed receipt whose recorded target sha256 no longer matches -> lock is
  still acquired (negative control; fails if the skip is made unconditional)

* ci: re-trigger PR checks after GitHub Actions internal job failures

No product change. Previous Yeachan-Heo#3150 run failed with empty-step jobs annotated
as "GitHub Actions has encountered an internal error when running your job".

* ci: re-trigger PR checks after Actions recovery

No product change. Previous re-trigger landed in the GHA internal-error window
(empty-step jobs). Dev CI has since recovered on post-merge runs.

---------

Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…3073)

Owner-approved MERGE_READY at exact head ce4f799 with green CI (28 checks). Merged after post-Yeachan-Heo#3135/Yeachan-Heo#3150 sequencing: current free-merge after green on f14e244, with Yeachan-Heo#3150 already serial-merged to a11fd9f.
@twoimo
twoimo force-pushed the successor/telegram-tool-activity-clean branch from 76e205b to f14e244 Compare July 25, 2026 14:13
@github-actions
github-actions Bot force-pushed the successor/telegram-tool-activity-clean branch from 591cc2c to b58c66a Compare July 25, 2026 14:21
@github-actions

Copy link
Copy Markdown

TELEGRAM_SUCCESSOR_VERIFIED

Yeachan-Heo pushed a commit that referenced this pull request Aug 12, 2026
Codex P2 review on PR Yeachan-Heo#4206 surfaced seven genuine defects; all are
documentation correctness fixes with no runtime impact:

- mkdir -p ManualBackups before ditto (ditto cannot create missing parent)
- invariant #11: SDK state lives in repo-local gitignored .gjc/state/, not
  'outside the repository' -- the original wording contradicted the guide's
  own discovery path at the SDK section
- model profile names: all four (frontier-heavy, gpt-heavy, glm-deepseek,
  kimi-gpt) are operator-defined, not just kimi-gpt; none ships with GJC
- fixed-folder cd: single-quoted cd -- '<path>' suppresses tilde expansion;
  document that the plugin must normalize ~ to $HOME before shell-quoting
- manifest path example: ~/src/gajae-code -> $HOME/src/gajae-code
- verification --outfile=~/tmp -> --outfile="$HOME/tmp/..." (no tilde
  expansion after =)
- SDK answer pad: clear only on action_resolved matching the displayed
  presentation id (matches telegram-reference.ts:547); reject controlled /
  multi-select asks before showing the fixed numeric pad

Lore-id: 4206-review-fixforward
Confidence: high
Scope-risk: docs-only
Tested: docs-index-lazy parity gate
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants